package in.ac.iitb.cse.nlp.postagger.data;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class TriViterbiHMM extends HMM {
	private int prevState[][];
	private int prevState2[][];
	private Double probMatrix[][];
	
	private int prevStateS1,prevStateS2;
	private int prevState2S1,prevState2S2;
	private int probMatrixS1,probMatrixS2;

	public TriViterbiHMM()
	{
		prevState = null;
		probMatrix = null;
		prevStateS1 = prevStateS2 = 0;
		probMatrixS1 = probMatrixS2 = 0;
		prevState2S1 = prevState2S2 = 0;
		
		prevState2S1 = 100;
		prevState2S2 = 100;
		prevState2 = new int [100+2][100+2];

		prevStateS1 = 100;
		prevStateS2 = 100;
		prevState = new int [100+2][100+2];

		probMatrixS1 = 100;
		probMatrixS2 = 100;
		probMatrix = new Double [100+2][100+2];
	}
	
	public void init()
	{
		if( tagset == null || sentence == null )
			System.err.println("BiViterbiHMM Clue 2");
		
		checkSentence();
		
		nTags = tagset.size();
		nWords = sentence.size();
		
		for(int i=0;i<nWords;i++)
			bestTagSeq.add("UNK");
		
		if( prevState ==null || nTags > prevStateS1 || nWords > prevStateS2 )
		{
			prevStateS1 = nTags;
			prevStateS2 = nWords;
			prevState = new int [nTags+2][nWords+2];
		}
		if( prevState2 ==null || nTags > prevState2S1 || nWords > prevState2S2 )
		{
			prevState2S1 = nTags;
			prevState2S2 = nWords;
			prevState2 = new int [nTags+2][nWords+2];
		}
		if( probMatrix ==null || nTags > probMatrixS1 || nWords > probMatrixS2 )
		{
			probMatrixS1 = nTags;
			probMatrixS2 = nWords;
			probMatrix = new Double [nTags+2][nWords+2];
		}
		
		//prevState = new int [nTags+2][nWords+2];
		//prevState2 = new int [nTags+2][nWords+2];
		//probMatrix = new Double [nTags+2][nWords+2];
		
		for( int i=0; i< nTags+2; i++)
			for( int j=0; j< nWords+2; j++){
				prevState[i][j] = prevState2[i][j] = 0;
				probMatrix[i][j] = 0.0;
			}
		
		nWords = nWords - 1; // ?? Dont consider state comes after Doller $
	}
	public void checkSentence()
	{
		super.checkSentence();
		if( sentence ==null ) return;
		if(sentence.size() > 1 && sentence.get(1).equalsIgnoreCase("^") == false ) sentence.add(0,"^");
	}
	public void makeSeq()
	{
		super.makeSeq();
		if( bestTagSeq ==null ) return;
		if( bestTagSeq.size() > 0 && bestTagSeq.get(0).equalsIgnoreCase("^") == true ) bestTagSeq.remove(0);
	}
	
	@SuppressWarnings("null")
	public List<String> bestProbTagSeq( List<String> Sentence)
	{
		bestTagSeq = new ArrayList<String>();
		sentence = Sentence;
		init();
				
		Double transProb,tProb,eProb;
		Double tProb1,tProb2,tProb3,eProb1,eProb2;
		Double prob;
		int stateIndex=0;
		
		Double h[] = new Double[3];
		
		//TriViterbiHMM.calcSmoothingFactors(tm, tagset);
		//System.out.println(" H1="+Config.h[0]+" H2="+Config.h[1]+" H3="+Config.h[2]);
		
		probMatrix[tagset.indexOf("^")][0] = 1.0; 
		probMatrix[tagset.indexOf("^")][1] = 1.0;
		
		prevState[tagset.indexOf("^")][1] = prevState2[tagset.indexOf("^")][1] = tagset.indexOf("^"); 
		
//		for( stateIndex=0; stateIndex<nWords; stateIndex++)
		for( stateIndex=0; stateIndex<nWords-1; stateIndex++)
		{
			// TODO
			//if ( preProcessInputWord(stateIndex, Sentence) !=0 )
				//continue;			
			
			///// Tagi belongs to state(stateIndex)
			for(int tagi=0; tagi < nTags; tagi++) 
			{
				String sTag1 = tagset.get(tagi);
//				String word1 = Sentence.get(stateIndex);
//				String word2 = Sentence.get(stateIndex+1);
				String word1 = Sentence.get(stateIndex+1);
				String word2 = Sentence.get(stateIndex+2);
				
				//eProb1 = em.getProbability(sTag1,word1);
//				if(probMatrix[tagi][stateIndex] != 0.0 && eProb1 != 0.0)
				if(probMatrix[tagi][stateIndex] != 0.0)
				{
					///// Tagj belongs to state(stateIndex+1)
					for(int tagj=0; tagj < nTags; tagj++)  
					{
						String sTag2 = tagset.get(tagj);
						Tag Tag2 = DataMaps.tags.get(sTag2);
						Tag Tag12 = DataMaps.tags.get(sTag1+"|"+sTag2);
						
						
						HashMap<Tag, Integer> hashMap=null;
						int CountTotal = 0;
						if(Tag2!=null)
						{
							hashMap=tm.transitionTable.get(Tag2);
							CountTotal = Tag2.getCount();
						}
						
						HashMap<Tag, Integer> hashMap2=null;
						int CountTotal2 = 0;
						if(Tag12!=null)
						{
						hashMap2=tm.transitionTable.get(Tag12);
						CountTotal2 = Tag12.getCount();
						}
						eProb1 = em.getProbability(sTag2,word1);
						//eProb2 = em.getProbability(sTag2,word2);
//						if(eProb2 != 0.0)
						if(eProb1 != 0.0)
						{
							for(int tagk=0; tagk < nTags; tagk++)  
							{
								String sTag3 = tagset.get(tagk);
								Tag Tag3 = DataMaps.tags.get(sTag3);

								tProb=tProb1=tProb2=tProb3=0.0;

								eProb2 = em.getProbability(sTag3,word2);
								
								/////		  WORD(stateIndex)        WORD(stateIndex+1)
								///// Tagi --------------------> Tagj -----------------> Tagk
								//tProb1 = DataMaps.tags.get( tagset.get(tagk)).getCount()/1.0;
								//tProb2 = tm.getProbability(tagset.get(tagj), tagset.get(tagk));
								//tProb3 = tm.getProbability(tagset.get(tagi)+"|"+tagset.get(tagj), tagset.get(tagk));

								tProb1 = 1.0*DataMaps.tags.get(sTag3).getCount()/Config.nTrainTags;
								if(CountTotal!=0&&hashMap !=null)
								{
									Integer c = hashMap.get(Tag3);
									if(c==null) c=0;
									tProb2 = 1.0*c/CountTotal;
								}
								if(CountTotal2!=0&&hashMap2 !=null)
								{
									Integer c = hashMap2.get(Tag3);
									if(c==null) c=0;
									tProb3 = 1.0*c/CountTotal2;
								}
																
								tProb = tProb1*Config.h[0] + tProb2*Config.h[1] + tProb3*Config.h[2];
								eProb = eProb1 * eProb2;
								transProb = tProb * eProb;
								
								///// S(ZERO) -> S(stateIndex-1) * transProb(stateIndex ---------> stateIndex+2 )
								prob = transProb * probMatrix[tagi][stateIndex];
								
								prob = prob * 10; // SCALE
								
								if( prob > probMatrix[tagk][stateIndex+2] )
								{
									probMatrix[tagk][stateIndex+2] = prob;
									prevState[tagk][stateIndex+2] = tagi;
									prevState2[tagk][stateIndex+2] = tagj;
								}
							}
						}
					}//End of for(int tagj=0; tagj < nTags; tagj++)					
				}//End of if(probMatrix[tagi][stateIndex] != 0.0)
			}//End of for(int tagi=0; tagi < nTags; tagi++)
			
			if( Config.isDebug)
			Dump(Sentence, tagset);
		}
				
		constructBestTagSeq(bestTagSeq, tagset);
		makeSeq();
		return bestTagSeq;
	}
	
	public int preProcessInputWord(int index, List<String> Sentence)
	{
		return 0;
	}
	
	public void constructBestTagSeq(List <String> bestTagSeq, List<String> Tagset)
	{
		int maxProbIndex,maxProbIndex1,maxProbIndex2;
		int lastindex = nWords+1;
		int tagindex = nWords+1;
		Double maxProb = probMatrix[0][lastindex];
		maxProbIndex = maxProbIndex1 = maxProbIndex2 = 0;
		do {
			for(int index = 1; index < nTags; index++)
			if(maxProb < probMatrix[index][lastindex]){
				maxProb = probMatrix[index][lastindex];
				maxProbIndex = index;
			}
			if(maxProb == 0.0){
				if(tagindex<=nWords) bestTagSeq.set(tagindex, "UNK");
				lastindex --;
				tagindex--;
			}else{
				if(tagindex<=nWords) bestTagSeq.set(tagindex, Tagset.get(maxProbIndex));
				tagindex--;
				break;
			}
		} while(maxProb == 0.0 && lastindex >0);
				
		
		for(int state = lastindex; state >= 0;  )
		{
			maxProbIndex2 = prevState2[maxProbIndex][state];
			if(tagindex>=0) 
				{
				 	bestTagSeq.set(tagindex, Tagset.get(maxProbIndex2));
					tagindex--;
				}
			maxProbIndex1 = prevState[maxProbIndex][state];
			if(tagindex>=0) 
				{
					bestTagSeq.set(tagindex, Tagset.get(maxProbIndex1));
					tagindex--;
				}
			maxProbIndex = maxProbIndex1;
			state = state - 2;
		}
		
		//System.out.println(bestTagSeq.toString());
		if(bestTagSeq.size()>0) bestTagSeq.remove(bestTagSeq.size()-1);//Remove Dollar
	}
	
	public void Dump(List <String> Sentence, List<String> Tagset)
	{
		try
		{
			File file = new File("/tmp/0000_nlp_prob_mat.csv");
			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
					new FileOutputStream(file)));
			
			writer.write("WORD,");

			for(int j=0; j< nWords; j++)
			{
				writer.write(Sentence.get(j)+",");
			}
			writer.newLine();
			for(int i=0; i< nTags; i++)
			{
				writer.write(Tagset.get(i)+",");
				for(int j=0; j< nWords+2; j++)
				{
					writer.write(probMatrix[i][j]+"["+prevState[i][j]+"]["+prevState2[i][j]+"],");
				}
				writer.write("END");
				writer.newLine();
			}
			writer.close();
		} catch(IOException e)
		{
			System.out.println(e);
		}
	}
	
	public boolean isTheCurrentStateOK(int state, List<String> Sentence, List<String> Tagset, TransitionMatrix tm, EmissionMatrix em)
	{
		int i,j;
		boolean isWorldAppeared = false;
		List<String> wordTagSet = new ArrayList<String>();
		List<String> tagTagSet = new ArrayList<String>();
		
		if(state < 1 ) return true;
		
		String word = Sentence.get(state-1);
		
		
		for(i=0; i<nTags; i++)
			if( 0.0 != probMatrix[i][state] )
				return true;

		//System.out.println(state + " " +(state-1)+" "+(state-2));
		
		// Is the alien world
		// Checking the Emission Table for the possibility of ZERO Values		
		for(i=0; i<nTags; i++)
			if( 0.0 != em.getProbability( Tagset.get(i) , word) )
			{
				isWorldAppeared = true;
				wordTagSet.add(Tagset.get(i));
			}
		if( false == isWorldAppeared )
		{
			System.out.println("I have never seen a word [[["+word+"]]].");
			return false;
		}
		
		if(state < 2 ) return false; //Can't find the reason possibly coding error
		
		//System.out.println(Sentence.get(state) + " " +Sentence.get(state-1)+" "+Sentence.get(state-2));

		
		// Checking the Transition Table for the possibility of ZERO Tag1 -> Tag2 combinations
		String pword = Sentence.get(state-2);
		
		for(i=0;i<nTags;i++)
			if(0.0 != probMatrix[i][state-1])
				tagTagSet.add(Tagset.get(i));
		
		if(0 == tagTagSet.size()) return false; //Can't find the reason

		for(i=0;i<tagTagSet.size();i++)
			for(j=0;j<wordTagSet.size();j++)
				if( true == tagTagSet.get(i).equals(wordTagSet.get(j)) && 0.0 != probMatrix[ Tagset.indexOf(tagTagSet.get(i)) ][state-1] )
				{
					System.out.println("The level with word [["+pword+"]] has the {"+ tagTagSet.toString() +"} non-zero probable tags/states");
					System.out.println("The next level with word [["+word+"]] tagged with {"+ wordTagSet.toString() +"} tags");
					System.out.println("On Emitting [["+word+"]] ,probMatrix["+ Tagset.indexOf(tagTagSet.get(i)) + "]["+(state-1)+"]=" + probMatrix[ Tagset.indexOf(tagTagSet.get(i)) ][state-1] +" has nonzero value for the column in ZERO");					
					return true; // true or false ?
				}
		
		System.out.println("The level with word [["+pword+"]] has the {"+ tagTagSet.toString() +"} non-zero probable tags/states");
		System.out.println("The next level with word [["+word+"]] tagged with {"+ wordTagSet.toString() +"} tags");
		System.out.println("All the possibilities two set of tags (P(tag1--(WORD)-->tag2)) are Zero");
		
		return false;
	}
	@Override

	public Double ProbTagSeq(List<String> Tagseq, List<String> Sentence)
	{
		Double transProb,tProb,eProb,FinalProp;
		int stateIndex=0;
		boolean isPrint = true;

		FinalProp = 1.0;
								
		for( stateIndex=0; stateIndex < Tagseq.size()-1; stateIndex++) 
		{
			
			System.out.print("P("+Tagseq.get(stateIndex)+"--"+Sentence.get(stateIndex)+"-->"+Tagseq.get(stateIndex+1)+") = ");

			eProb = em.getProbability(Tagseq.get(stateIndex),Sentence.get(stateIndex)); //Ulta
			if( true == isPrint)
			System.out.print("P("+Sentence.get(stateIndex)+"/"+Tagseq.get(stateIndex)+") * ");

			tProb = tm.getProbability(Tagseq.get(stateIndex), Tagseq.get(stateIndex+1)); //Ulta
			if( true == isPrint)
			System.out.print("P("+Tagseq.get(stateIndex+1)+"/"+Tagseq.get(stateIndex) +") = ");
			
			transProb = tProb * eProb;
			if( true == isPrint)
			System.out.print(" "+eProb + " * "+ tProb +" = "+  transProb);
			
			FinalProp = FinalProp * transProb ;
			
			//if( true == isPrint)
			///System.out.print(" ["+FinalProp+"]");
			
			if( true == isPrint)
			System.out.print("\n");			
		
		}				
		
		return FinalProp;
	}



	@Override
	public Tag handleUnknownWord(int index) {
		Tag t = DataMaps.tags.get("VVB");
		return t;
	}
	
	public static void calcSmoothingFactors(TransitionMatrix tm, List<String> tagset)
	{
		int nTotal = tagset.size();
		int f1f2f3,f1f2;
		int f2f3,f2;
		int f3,fn;
		Double tri,uni,bi;
		String s1,s2,s3, prev;
		
		fn = 0;
		Config.h[0] = Config.h[1] = Config.h[2] = 0.0;
		
		for(int i=0;i<nTotal;i++)
		{
			s2 = tagset.get(i);
			fn += DataMaps.tags.get(s2).getCount();
		}
		
		//fn=(int)Config.nTrainWords;
		Config.nTrainTags = fn;
		
		for(int i=0;i<nTotal;i++)
		{	
			s1 = tagset.get(i);
			for(int j=0;j<nTotal;j++)
			{
				s2 = tagset.get(j);
				prev = s1 + "|" + s2; 
				f1f2 = tm.getCount(s1, s2);
				f2 = DataMaps.tags.get(s2).getCount();
				for(int k=0;k<nTotal;k++)
				{
					s3 = tagset.get(k);
					f1f2f3 = tm.getCount(prev, s3);
					if(f1f2f3 != 0)
					{
						tri = bi = uni = 0.0;
						f2f3 = tm.getCount(s2, s3);
						f3 = DataMaps.tags.get(s3).getCount();
						if( f1f2 > 1)
						tri = 1.0*(f1f2f3-1) / (f1f2-1);
						if( f2 > 1)
						bi = 1.0*(f2f3-1) / (f2-1);
						if( fn > 0)
						uni = 1.0*(f3) / (fn);
						if( uni > bi ){	
							if( uni > tri)
								Config.h[0] += uni;
							else
								Config.h[2] += tri;
						}else{
							if ( bi > tri )
								Config.h[1] += bi;
							else
								Config.h[2] += tri;
						}
				}//if
			}//for
		}//for
		}//for
		
		//Normalize
		Double t = Config.h[0] + Config.h[1] + Config.h[2];
		Config.h[0] /= t;
		Config.h[1] /= t;
		Config.h[2] /= t;
		return;
	}
}

